/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simulation;

import jade.content.lang.sl.SLCodec;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.ContainerID;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.domain.FIPANames;
import jade.domain.JADEAgentManagement.CreateAgent;
import jade.domain.JADEAgentManagement.JADEManagementOntology;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.AchieveREInitiator;
import logging.LogSetup;
import org.apache.log4j.Logger;
import persistence.DataWriter;

/**
 * The controller of the simulation
 * @author Danielondon
 */
public class SimulationControllerAgent extends Agent {
    protected Logger logger = LogSetup.getLogger();
    private SimulationView view; 
    /**
     * the name of the Id where the container will open the Simulation agents.
     */
    private String containerId;
    private SimulationModel simulationModel;
    private boolean isFirstSimulation;
    
    /**
     * Initialize the Simulation Controller Agent.
     */
    @Override
    protected void setup(){
        this.containerId = "Main-Container";
        System.out.println("Starting up SimulationController");
        this.simulationModel = new SimulationModel();
        this.view = new SimulationView(this);
        this.simulationModel.addObserver(this.view);
        this.isFirstSimulation = true;
        view.showGui();
        ServiceDirectory.registerAgentService(this, ServiceSimulationDescription.SIMULATION_LISTENER);
        // Adds the siulation behavior to communication with the Simulation Controller
        this.addBehaviour(new ListenSimulationBehaviour());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            logger.error(ex);
        }
        // Start Market Agent
        this.createMarketAgent();
    }   
    
    /**
     * Closing agent
     */
    @Override
    protected void takeDown(){
        this.view.dispose();
        System.out.println("Closing down SimulationController");
    }

    public void addCar(String carSpecs, String drivingPattern, String chargingStrategy, int count) {
        for (int i =0; i < count; i++){
            simulationModel.addCar("", carSpecs, drivingPattern, chargingStrategy);
        }        
        this.simulationModel.notifyObservers();
    }
    
    public void setSimulation(int totalSlots, int slotsPerHour, String marketFile){
        // Define Parameters;
        this.simulationModel.setTotalSlots(totalSlots, CommonResources.DEFAULT_INTERVAL / slotsPerHour);
        
        if (!simulationModel.isMarketReady()){
            logger.info("Market is not ready. Simulation can not be set.");
            return;
        }
        
        addBehaviour(new SendMessage(getStringMarketArguments(marketFile), simulationModel.getMarket(), ACLMessage.REQUEST));

        // Create Agents. Check that the ones that were already created, 
        // are not created again.
        for (int i = 0; i < this.simulationModel.getCarsCount(); i++) {
            AgentInfo carAgent = this.simulationModel.getCarAgent(i);
            if (carAgent.getName().equals("")) {
                String agentName = this.createCarAgent(carAgent.getCarSpecs(), carAgent.getDrivingPattern(), carAgent.getChargingStrategy());
                this.simulationModel.setAgentName(i, agentName);
            } else {
                // Send Simulation Parameters
                // Remember that the id is set once the Simulation starts and the agent
                // starts passign information back.
                if (carAgent.getId() != null) {
                    addBehaviour(new SendMessage(this.getStringCarArguments(carAgent.getCarSpecs(), carAgent.getDrivingPattern(), carAgent.getChargingStrategy()), carAgent.getId(), ACLMessage.REQUEST));
                } else {
                    System.out.println("Id for the agent info " + carAgent.getName() + " is null. No messages can be sent");
                }                
            }
        }
       
        this.simulationModel.notifyObservers();
        this.isFirstSimulation = false;
    }

    /**
     * Creates the Market Agent.
     */
    private void createMarketAgent() {
        if (this.isFirstSimulation) {
            try {        
                this.createAgent(this.getMarketArguments(DataWriter.MARKET_DATA_FILE), MarketAgent.class.getName(), "MarketAgent");
            } catch (Exception ex) {
                logger.error(ex);
            }
        }
    }
    
    /**
     * Creates an agent car
     * @param carSpecs, String drivingPattern, String chargeStrategy
     * @return 
     */
    private String createCarAgent(String carSpecs, String drivingPattern, String chargeStrategy) {
        String name = null;
        try {        
            name = this.createAgent(
                    this.getCarArguments(carSpecs, drivingPattern, chargeStrategy), 
                    CarAgent.class.getName(), 
                    simulationModel.getAgentName());
        } catch (Exception ex) {
            logger.error(ex);
        }
        
        return name;
    }
    
    /**
     * Get the arguments of the Model
     * @param carModel
     * @return 
     */
    private Object[] getCarArguments(String carSpecs, String drivingPattern, String chargeStrategy) {
        Object [] arguments = new Object[6];        
        arguments[0] = this.getAID();
        arguments[1] = simulationModel.getMarket();
        arguments[2] = this.simulationModel.getTotalSlots() + CommonResources.STRING_SEPARATOR + this.simulationModel.getSlotSize();
        arguments[3] = carSpecs;
        arguments[4] = drivingPattern;
        arguments[5] = chargeStrategy;
        return arguments;
    }
    
    private Object[] getMarketArguments(String fileName){
        Object [] arguments = new Object[4];        
        arguments[0] = this.getAID();
        arguments[1] = new AID();
        arguments[2] = this.simulationModel.getTotalSlots() + CommonResources.STRING_SEPARATOR + this.simulationModel.getSlotSize();
        arguments[3] = fileName;
        return arguments;
    }

    /**
     * Creates an agent.
     * @param arguments
     * @param className
     * @param name
     * @return 
     */
    private String createAgent(Object [] arguments, String className, String name) throws Exception {
        final CreateAgent ca = new CreateAgent();
        ca.setAgentName(name);
        ca.setClassName(className);
        ca.setContainer(new ContainerID(containerId, null));
        for(Object argument:arguments) {
            ca.addArguments(argument);
        }
        Action actExpr = new Action(getAMS(), ca);
        ACLMessage request = new ACLMessage(ACLMessage.REQUEST);
        request.addReceiver(getAMS());
        request.setOntology(JADEManagementOntology.getInstance().getName());
        getContentManager().registerLanguage(new SLCodec(), FIPANames.ContentLanguage.FIPA_SL);
        getContentManager().registerOntology(JADEManagementOntology.getInstance());
        request.setLanguage(FIPANames.ContentLanguage.FIPA_SL);
        request.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);        
        getContentManager().fillContent(request, actExpr);
        addBehaviour(new AchieveREInitiator(this, request) {
            protected void handleInform(ACLMessage inform) {                
                //System.out.println("Agent successfully created " + inform.getContent() );                   
            }

            protected void handleFailure(ACLMessage failure) {
                System.out.println("Error creating agent " + ca.getAgentName() + "  " + failure);
            }
        });       
        
        return ca.getAgentName();
    }
    
    /**
     * Starts the Simulation
     */
    public void startSimulation() {
        if(!this.simulationModel.isSimulationStarted()){
            addBehaviour(new StartSimulationBehaviour());            
        }
    }

    private String getStringCarArguments(String carSpecs, String drivingPattern, String chargeStrategy) {
        Object [] arguments = this.getCarArguments(carSpecs, drivingPattern, chargeStrategy);
        return CommonResources.ParametersToString(arguments);
    }
    
    private String getStringMarketArguments(String marketFile) {
        Object [] arguments = this.getMarketArguments(marketFile);
        return CommonResources.ParametersToString(arguments);
    }

    public boolean saveResults(String fileName, String extensionData, String extensionPlot) {
        return this.simulationModel.resultsToFile(fileName, extensionData, extensionPlot);
    }

    void changeChargeStrategy(String newStrategy, int[] carIndexes) {
        this.simulationModel.setNewStrategy(newStrategy, carIndexes);
        this.simulationModel.notifyObservers();
    }

    void createMarketData(String filename, double dayPrice, double endPrice, int slotCount, boolean dualPriceModel, double dualStart, double dualEnd) throws Exception {
        MarketFactory.generateMarket(filename, dayPrice, endPrice, slotCount, dualPriceModel, dualStart, dualEnd);
    }
    
    /**
     * Behavior to start the Simulation
     */
    private class StartSimulationBehaviour extends OneShotBehaviour {
        @Override
        public void action() {           
            AID [] simulationAgents = new AID[0];
            simulationAgents = ServiceDirectory.getProvidersFromService(myAgent, ServiceSimulationDescription.SIMULATION);

            // Send the Simulation Message
            if (simulationAgents.length > 0){
                System.out.println("Agents founded to simulate " + simulationAgents.length);
                // Send the cfp to all sellers
                ACLMessage cfp = new ACLMessage(ACLMessage.AGREE);
                for (int i = 0; i < simulationAgents.length; ++i) {
                    cfp.addReceiver(simulationAgents[i]);
                } 
                cfp.setContent("Start");
                cfp.setConversationId("start simulation");
                cfp.setReplyWith("simulation"+System.currentTimeMillis()); // Unique value
                myAgent.send(cfp);
            }
        }    
    }
    
    /**
     * Behavior to keep track of the notifications of each agent.
     */
    private class ListenSimulationBehaviour extends CyclicBehaviour {

        @Override
        public void action() {
            MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.INFORM);
            ACLMessage msg = myAgent.receive(mt);
            if (msg != null) {                
                String message = msg.getContent();
                //logger.info("Receiving message " + message);
                AID sender = msg.getSender();
                if (msg.getConversationId().equals(CommonResources.SIMULATION_CONVERSATION_ID)){
                    if (message.equals("market")){        
                        simulationModel.setMarket(sender);                        
                    } else  {                        
                        simulationModel.updateModel(sender, message);                        
                    }
                    
                    simulationModel.notifyObservers();
                } if (msg.getConversationId().equals("endSimulation")){
                    String[] temp = message.split("&&&");
                    simulationModel.updateModel(sender, temp[0]);
                    SimulationResult result = SimulationResult.fromString(temp[1]);
                    simulationModel.addResult(sender, result);
                    simulationModel.notifyObservers();
                }
            } else {
                this.block();
            }            
        }        
    }
}
